package online.inote.naruto.security.core;

import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import online.inote.naruto.annotation.token.WebSecurity;
import online.inote.naruto.cache.CacheSupport;
import online.inote.naruto.common.global.enable.EnableGlobalResultHandle;
import online.inote.naruto.common.utils.response.Code;
import online.inote.naruto.common.utils.response.ExtendResponse;
import online.inote.naruto.common.utils.response.Response;
import online.inote.naruto.security.props.TokenProperties;
import online.inote.naruto.security.utils.JwtHelper;
import online.inote.naruto.utils.Assert;
import online.inote.naruto.utils.StringUtils;
import org.apache.commons.collections4.MapUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;
import org.springframework.http.MediaType;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.reflect.Method;
import java.nio.charset.StandardCharsets;
import java.util.Map;

/**
 * @description Token处理拦截器
 * @author gaopengsui@creditease.cn
 * @date 2019年4月29日 下午6:28:23
 */
@Slf4j
public class TokenHandlerInterceptor implements HandlerInterceptor {

  @Autowired private TokenProperties props;
  @Autowired private ApplicationContext context;

  @Override
  public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
      throws Exception {

    if (handler instanceof HandlerMethod) {
      Method method = ((HandlerMethod) handler).getMethod();
      WebSecurity security = null;
      String path = request.getServletPath();

      log.info("请求路径：" + path);

      if (method.isAnnotationPresent(WebSecurity.class)) {
        security = method.getAnnotation(WebSecurity.class);
      }

      if (security == null || security.required()) {
        Response<Object> result = checkToken(request);

        if (!StringUtils.equals(Code.SUCCESS.getCode(), result.code)) {

          if (isEnableGlobalResultHandle()) {
            returnError(response, result);
            return false;
          } else {
            log.info("Token验证未通过,原因:[ {} ]", result.message);
            throw new RuntimeException(result.message);
          }
        }
      }
    }

    return HandlerInterceptor.super.preHandle(request, response, handler);
  }

  @Override
  public void afterCompletion(
      HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
    String token = request.getHeader(props.getHeader().getKey());

    if (StringUtils.isNotBlank(token)) {
      CacheSupport.cache(getTokenCacheKey(token), token, props.getCache().getExpireTime());
    }
  }

  /**
   * @description Token认证
   * @author XQF.Sui
   * @created 2019年4月29日 下午7:23:17
   * @since 1.0
   * @return 检查结果
   */
  private Response<Object> checkToken(HttpServletRequest request) {

    String token = request.getHeader(props.getHeader().getKey());
    Assert.notBlank(token, "Token不能为空");

    try {
      String redisToken = CacheSupport.get(getTokenCacheKey(token));

      if (StringUtils.isBlank(redisToken)) {
        log.info("Token认证失败,Token登录超时");
        return ExtendResponse.create(Code.FAIL.getCode(), "Token登录超时");
      }

      if (!StringUtils.equals(redisToken, token)) {
        log.info("Token认证失败,已在其他地方登陆");
        return ExtendResponse.create(Code.FAIL.getCode(), "账号已在其他地方登陆");
      }
    } catch (Exception e) {
      log.error("Token验证异常", e);
      return ExtendResponse.create(Code.FAIL.getCode(), "Token验证异常");
    }

    return ExtendResponse.create(Code.SUCCESS.getCode(), "Token验证异常");
  }

  private String getTokenCacheKey(String token) {
    return props.getCache().getPrefix() + JwtHelper.getClaims(token).getBody().getId();
  }

  private boolean isEnableGlobalResultHandle() {
    Map<String, Object> beanMap = context.getBeansWithAnnotation(SpringBootApplication.class);

    if (MapUtils.isEmpty(beanMap)) {
      return false;
    }

    return beanMap
        .values()
        .toArray()[0]
        .getClass()
        .getSuperclass()
        .isAnnotationPresent(EnableGlobalResultHandle.class);
  }

  private void returnError(HttpServletResponse response, Response<Object> result)
      throws IOException {
    response.setContentType(MediaType.APPLICATION_JSON_VALUE);
    response.setCharacterEncoding(StandardCharsets.UTF_8.name());
    try (PrintWriter writer = response.getWriter()) {
      writer.write(JSON.toJSONString(result));
      writer.flush();
    }
  }
}
